package  org.ttitfly.ttcore.util.query;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.QueryException;

public class SQueryBuilder {

    private static final Log logger = LogFactory.getLog(SQueryBuilder.class);

    public static String PRE_SEPARATOR = "/~";

    public static String POST_SEPARATOR = "~/";

   
    public static Query buildQuery(EntityManager em, String pattern, ParameterResolver resolver) throws QueryException {
        /**
         * restrict by domain
         */
        // pattern = restrictByDomain(pattern, resolver);
        // parse pattern string
        String[] criteria = StringUtils.substringsBetween(pattern, PRE_SEPARATOR, POST_SEPARATOR);
        if (criteria == null) {
            if (resolver.isSortable()) {
                pattern = buildSortQueryString(pattern, resolver);
            }
            return em.createQuery(pattern);
        }

        Map<String, String> criterionMap = new HashMap<String, String>();
        Map<String, Object> parameterMap = new HashMap<String, Object>();

        buildMap4Query(criterionMap, parameterMap, criteria, resolver);

        String queryString = new StrSubstitutor(criterionMap, PRE_SEPARATOR, POST_SEPARATOR).replace(pattern);

        if (resolver.isSortable()) {
            queryString = buildSortQueryString(queryString, resolver);
        }

        // create query
        Query query = null;
        try {
            query = em.createQuery(queryString);
            logger.trace("query string: " + queryString);
        } catch (Exception e) {
            logger.error("Failed when create Query.", e);
            throw new QueryException("Failed when create Query.", e);
        }

        buildParaQuery(parameterMap, query);

        if (resolver.isPagable() && queryString.toLowerCase().indexOf("count(") < 0) {
            buildPagingQuery(query, resolver);
        }

        return query;

    }

    private static String buildSortQueryString(String queryString, ParameterResolver resolver) {
        if (null == resolver.getObject()) {
            logger.error("No SO object to get");
            throw new QueryException("Failed when create Query,reson:No SO object to get");
        }
        Sortable sortObject = (Sortable) resolver.getObject();

        String[] aliasArr = StringUtils.substringsBetween(queryString, "[", "]");

        queryString = queryString.replace("[" + StringUtils.join(aliasArr, "") + "]", "");

        if (sortObject.getSorts() == null || sortObject.getSorts().size() == 0) {
            queryString = queryString.replace("{", " ").replace("}", " ");
        } else {
            String[] defualtOrderBy = StringUtils.substringsBetween(queryString, "{", "}");

            if (null != defualtOrderBy && defualtOrderBy.length > 0) {
                if (aliasArr == null) {
                    throw new QueryException("Failed when create Query,reson:Alias name lost");
                }

                String orderBy = "";
                String alias = StringUtils.join(aliasArr, "").split("=")[1];

                for (Sort sort : sortObject.getSorts()) {
                    if (sort.getSortKey() != null) {
                        if (orderBy.equals(""))
                            orderBy += alias + "." + sort.getSortKey() + " " + sort.getSortDir();
                        else
                            orderBy += "," + alias + "." + sort.getSortKey() + " " + sort.getSortDir();
                    }
                }

                if (orderBy.equals("")) {
                    queryString = queryString.replace("{", "").replace("}", "");
                    return queryString;

                }

                Map<String, String> defualtOrderByMap = new HashMap<String, String>();
                defualtOrderByMap.put(defualtOrderBy[0], orderBy);
                queryString = new StrSubstitutor(defualtOrderByMap, "{", "}").replace(queryString);

            }
        }

        return queryString;
    }

    private static void buildParaQuery(Map<String, Object> parameterMap, Query query) {
        // set parameters into query
        Set<String> parameterNames = parameterMap.keySet();
        for (String parameterName : parameterNames) {
            if (parameterMap.get(parameterName) instanceof String) {
                query.setParameter(parameterName, ((String) parameterMap.get(parameterName)).toUpperCase());
            } else {
                query.setParameter(parameterName, parameterMap.get(parameterName));
            }

        }

    }

    private static void buildMap4Query(Map<String, String> criterionMap, Map<String, Object> parameterMap, String[] criteria,
            ParameterResolver resolver) {

        for (String criterion : criteria) {
            String parameterName = StringUtils.substringAfter(criterion, ":").trim().replace(")", "").replace(",", "");
            if (StringUtils.contains(parameterName, " ")) {
                parameterName = parameterName.substring(0, parameterName.indexOf(" "));
            }
            // if parameterName started with "%"
            boolean havePrefix = false;
            if (parameterName.length() > 1 && parameterName.charAt(0) == '%') {
                havePrefix = true;
                parameterName = parameterName.substring(1);
            }
            // if parameterName end with "%"
            boolean haveSuffix = false;
            if (parameterName.length() > 1 && parameterName.charAt(parameterName.length() - 1) == '%') {
                haveSuffix = true;
                parameterName = parameterName.substring(0, parameterName.length() - 1);
            }

            Object parameterValue = resolver.getValue(parameterName);
            if (null != parameterValue && !parameterValue.equals("")) {
                criterionMap.put(criterion, criterion.replace("%", ""));
                if (havePrefix == true || haveSuffix == true) {
                    parameterMap.put(parameterName, (havePrefix ? "%" : "") + (String) parameterValue + (haveSuffix ? "%" : ""));
                } else {
                    parameterMap.put(parameterName, parameterValue);
                }
            } else {
                criterionMap.put(criterion, "");
            }
        }
    }

    private static void buildPagingQuery(Query query, ParameterResolver resolver) {
        if (null == resolver.getObject()) {
            logger.error("No SO object to get");
            throw new QueryException("Failed when create Query,reson:No SO object to get");
        }
        Pagable page = (Pagable) resolver.getObject();
        if (page.isExportFull()) {
            return;
        }
        int curPage = page.getCurrentPage();
        int pageSize = page.getPageSize();
        if (curPage > 0 && pageSize > 0)
            query.setMaxResults(pageSize).setFirstResult((curPage - 1) * pageSize);

    }

    public static Query buildUniqueQuery(EntityManager em, String pattern, ParameterResolver resolver) throws QueryException {
        // parse pattern string
        String[] criteria = StringUtils.substringsBetween(pattern, PRE_SEPARATOR, POST_SEPARATOR);
        if (criteria == null) {
            return em.createQuery(pattern);
        }

        Map<String, String> criterionMap = new HashMap<String, String>();
        Map<String, Object> parameterMap = new HashMap<String, Object>();

        buildMap4Query(criterionMap, parameterMap, criteria, resolver);

        String queryString = new StrSubstitutor(criterionMap, PRE_SEPARATOR, POST_SEPARATOR).replace(pattern);

        // create query
        Query query = null;
        try {
            query = em.createQuery(queryString);
        } catch (Exception e) {
            logger.error("Failed when create Query.", e);
            throw new QueryException("Failed when create Query.", e);
        }

        buildParaQuery(parameterMap, query);

        return query;
    }

}
